package mes.kanban.config;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import mes.kanban.entity.MesPermission;
import mes.kanban.service.MesPermissionServiceI;

/**
 * Apache Shiro 核心通过 Filter 来实现，就好像SpringMvc 通过DispachServlet 来主控制一样。
 *  既然是使用 Filter 一般也就能猜到，是通过URL规则来进行过滤和权限校验，所以我们需要定义一系列关于URL的规则和访问权限。
 * @author gjy
 *
 * 2018年2月11日下午1:42:13
 */
@Configuration
public class ShiroConfig {
	
	
	private static final Logger log = LoggerFactory.getLogger(ShiroConfig.class);
	
	private String algorithmName = "md5";
	
    private int hashIterations = 2;
    
    @Value("${spring.redis.url}")
	private String host;
	
	@Value("${spring.redis.port}")
	private int port;
	
	@Value("${shiro.securityManage.timeout}")
	private int shiroTimeout;
	
	@Autowired
    private MesPermissionServiceI mesPermissionServiceI;
	
	@Bean
	public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
		log.info("ShiroConfiguration.shirFilter()");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		
		// 必须设置 SecurityManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/login");
		
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		
		//未授权界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");
		
		Map<String, String> filterChainDefinitionMap = loadFilterChainDefinitions();
		/*
		 * 过滤链定义，从上向下顺序执行，一般将/**放在最为下边 :这是一个坑呢，一不小心代码就不好使了;
		 * authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
		 */
		filterChainDefinitionMap.put("/**", "authc");
		
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		
		return shiroFilterFactoryBean;
	}
	
	
	/**
     * ShiroDialect，为了在thymeleaf里使用shiro的标签的bean
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

	/**
	 * 凭证匹配器
	 * （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * ）
	 * @return
	 */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher(){
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		
		hashedCredentialsMatcher.setHashAlgorithmName(algorithmName);//散列算法:这里使用MD5算法;
		
		hashedCredentialsMatcher.setHashIterations(hashIterations);//散列的次数，比如散列两次，相当于 md5(md5(""));
		
		return hashedCredentialsMatcher;
	}
	
	@Bean
	public MyShiroRealm myShiroRealm(){
		MyShiroRealm myShiroRealm = new MyShiroRealm(algorithmName,hashIterations);
		myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return myShiroRealm;
	}


	/**
	 * 使用redis管理serssion,减少数据库压力
	 * @return
	 */
	@Bean
	public SecurityManager securityManager(){
		DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
		// 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(SessionManager());
        
        securityManager.setRealm(myShiroRealm());
        
		return securityManager;
	}

	/**
     * cacheManager 缓存 redis实现
     * 
     * @return
     */
	public CacheManager cacheManager() {
		RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
	}
	
	/**
	 * 配置shiro的redis管理
	 * @return
	 */
	public RedisManager redisManager() {
		
		RedisManager redisManager = new RedisManager();
		
		redisManager.setHost(host);
		redisManager.setPort(port);
		redisManager.setExpire(shiroTimeout);
		
		return redisManager;
	}
	
	/**
     * shiro session的管理
     */
    public DefaultWebSessionManager SessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        
        sessionManager.setSessionDAO(redisSessionDAO());
        
        return sessionManager;
    }

	/**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
    	RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        
        return redisSessionDAO;
    }
    
	/**
	 *  开启shiro aop注解支持.
	 *  使用代理方式;所以需要开启代码支持;
	 * @param securityManager
	 * @return
	 */
	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
		return authorizationAttributeSourceAdvisor;
	}

	@Bean(name="simpleMappingExceptionResolver")
	public SimpleMappingExceptionResolver
	createSimpleMappingExceptionResolver() {
		SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
		Properties mappings = new Properties();
		mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
		mappings.setProperty("UnauthorizedException","403");
		r.setExceptionMappings(mappings);  // None by default
		r.setDefaultErrorView("error");    // No default
		r.setExceptionAttribute("ex");     // Default is "exception"
		return r;
	}
	
	/**
	 * 初始化权限
	 * @return
	 */
	@Bean(name="loadFilterChainDefinitions")
	public Map<String, String> loadFilterChainDefinitions() {
		// 权限控制map.从数据库获取
		Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
		/*
		 *  配置不会被拦截的链接 顺序判断(anon:所有url都都可以匿名访问),这个一定要这么配置，否则shiro会把所有都拦截，
		 *  尽管配置了/static/**,尽管anon,尽管把/**放在最后面，都会被拦截，只有这么配置才不会被拦截
		 */
		filterChainDefinitionMap.put("/static/**", "anon");
		//讨厌的每次都要去下载这个图标影响体验，因为这个是shiro用来展示的,所以不要让他在权限内才能够制止这种功能
		filterChainDefinitionMap.put("/favicon.ico", "anon");
		filterChainDefinitionMap.put("/css/**", "anon");
		filterChainDefinitionMap.put("/img/**", "anon");
		filterChainDefinitionMap.put("/js/**", "anon");
		filterChainDefinitionMap.put("/video/**", "anon");
		filterChainDefinitionMap.put("/composents/**", "anon");
		filterChainDefinitionMap.put("/userMessage/saveUser", "anon");
		filterChainDefinitionMap.put("/userMessage/getPassword", "anon");
		filterChainDefinitionMap.put("/loginjk/**", "anon");
		
		filterChainDefinitionMap.put("/table", "anon");
		
		
		//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
		filterChainDefinitionMap.put("/logout", "logout");
		
		List<MesPermission> resourcesList = mesPermissionServiceI.getPermissions();
        for(MesPermission resources:resourcesList){

            if (resources.getUrl()!=null) {
                String permission = "perms[" + resources.getUrl()+ "]";
                filterChainDefinitionMap.put(resources.getUrl(),permission);
            }
        }
		
		
		
		return filterChainDefinitionMap;
	}
	
	@Bean(name = "exceptionHandler")
    public HandlerExceptionResolver handlerExceptionResolver() {
        return new MyExceptionHandler();
    }
	
}